namespace SolidMvvm {
	using System;
	using System.Runtime.InteropServices;
	using System.Text;

	public static class Conversions {
		/// <summary>
		///     I found this EncodingType enum and ByteArrayToString at
		///     http://beecy.net/post/2009/01/10/byte-array-to-string-csharp.aspx
		/// </summary>
		public enum EncodingType {
			/// <summary>
			/// </summary>
			ASCII,

			/// <summary>
			/// </summary>
			Unicode,

			/// <summary>
			/// </summary>
			UTF7,

			/// <summary>
			/// </summary>
			UTF8
		}

		public const double EPSILON = 0.00001;

		public static string ByteArrayToHexString(byte[] inByte) {
			return BitConverter.ToString(inByte).Replace("-", string.Empty);
		}

		public static short ByteArrayToShort(byte[] inByte) {
			return Convert.ToInt16(inByte[0] + (inByte[1] * 256));
		}

		/// <summary>
		///     Converts a byte array to a string using specified encoding.
		///     I found this EncodingType enum and ByteArrayToString at
		///     http://beecy.net/post/2009/01/10/byte-array-to-string-csharp.aspx
		/// </summary>
		/// <param name="bytes"> Array of bytes to be converted. </param>
		/// <param name="encodingType"> EncodingType enum. </param>
		/// <returns> string </returns>
		public static string ByteArrayToString(byte[] bytes, EncodingType encodingType) {
			Encoding encoding;
			switch (encodingType) {
				case EncodingType.ASCII:
					encoding = new ASCIIEncoding();
					break;
				case EncodingType.Unicode:
					encoding = new UnicodeEncoding();
					break;
				case EncodingType.UTF7:
					encoding = new UTF7Encoding();
					break;
				case EncodingType.UTF8:
					encoding = new UTF8Encoding();
					break;
				default:
					throw new InvalidOperationException("Enum '" + encodingType + "' not recognized.");
			}
			return encoding.GetString(bytes);
		}

		public static ushort ByteArrayToUInt16(byte[] value) {
			return BitConverter.ToUInt16(GetPaddedByteArray(value, 2), 0);
		}

		public static uint ByteArrayToUInt32(byte[] value) {
			return BitConverter.ToUInt32(GetPaddedByteArray(value, 4), 0);
		}

		public static ushort ByteArrayToUShort(byte[] inByte) {
			return Convert.ToUInt16(inByte[0] + (inByte[1] * 256));
		}

		/// <summary>
		///     Return whether a single bit in the source byte array is set or not.
		/// </summary>
		public static bool GetBit(byte[] source, int offsetInBits) {
			return (source[offsetInBits / 8] & (1 << (offsetInBits % 8))) > 0;
		}

		/// <summary>
		///     Returns an individual bitfield value given its place in a byte array. Place being its bitfield length & offset.
		///     Offset 0 means "least significant bit."
		/// </summary>
		public static byte[] GetBits(byte[] source, int numberOfBits, int offsetInBits) {
			var result = new byte[GetByteIndexForBitIndex(numberOfBits - 1) + 1];
			for (var bitIndex = 0; bitIndex < numberOfBits; bitIndex++)
				SetBit(result, bitIndex, GetBit(source, bitIndex + offsetInBits));
			return result;
		}

		/// <summary>
		///     Return a zero-based index.
		/// </summary>
		/// <param name="bitIndex"> </param>
		/// <returns> </returns>
		public static int GetByteIndexForBitIndex(int bitIndex) {
			return bitIndex / 8;
		}

		public static T[] GetNewReversedArray<T>(T[] originalArray) {
			var result = new T[originalArray.Length];
			Array.Copy(originalArray, result, originalArray.Length);
			Array.Reverse(result);
			return result;
		}

		/// <summary>
		///     This adds bytes to higher indices to pad the given byte array up to 'totalBytes'.
		/// </summary>
		public static byte[] GetPaddedByteArray(byte[] originalByteArray, int totalBytes) {
			if (totalBytes < originalByteArray.Length)
				throw new ArgumentOutOfRangeException("totalBytes");
			var newByteArray = new byte[totalBytes];
			for (var index = 0; index < originalByteArray.Length; index++)
				newByteArray[index] = originalByteArray[index];
			return newByteArray;
		}

		/// <summary>
		///     Hex can have a prefix of 0x or just x. Binary can have a prefix of 0b or just b.
		/// </summary>
		public static int HexOrBinaryOrDecimalStringToInt(string hexOrBinaryOrDecimalString) {
			if (hexOrBinaryOrDecimalString.Length == 0)
				throw new ArgumentOutOfRangeException("Hex/Binary/Decimal string must be length greater than zero.");

			if (hexOrBinaryOrDecimalString.Length > 2 && hexOrBinaryOrDecimalString.Substring(0, 2) == "0b")
				return Convert.ToInt32(hexOrBinaryOrDecimalString.Substring(2), 2);
			if (hexOrBinaryOrDecimalString.Length > 1 && hexOrBinaryOrDecimalString[0] == 'b')
				return Convert.ToInt32(hexOrBinaryOrDecimalString.Substring(1), 2);
			if (hexOrBinaryOrDecimalString.Length > 2 && hexOrBinaryOrDecimalString.Substring(0, 2) == "0x")
				return Convert.ToInt32(hexOrBinaryOrDecimalString.Substring(2), 16);
			if (hexOrBinaryOrDecimalString.Length > 1 && hexOrBinaryOrDecimalString[0] == 'x')
				return Convert.ToInt32(hexOrBinaryOrDecimalString.Substring(1), 16);
			return Convert.ToInt32(hexOrBinaryOrDecimalString);
		}

		/// <summary>
		///     Converts a hex string (e.g. "AB01") to a byte array
		/// </summary>
		public static byte[] HexStringToByteArray(string sourceHexString) {
			var resultingByteArray = new byte[sourceHexString.Length / 2];
			for (var currentCharacterPairIndex = 0; currentCharacterPairIndex < sourceHexString.Length; currentCharacterPairIndex += 2)
				resultingByteArray[currentCharacterPairIndex / 2] = Convert.ToByte(sourceHexString.Substring(currentCharacterPairIndex, 2), 16);

			return resultingByteArray;
		}

		public static int NumberOfSignificantBits(byte[] value) {
			for (var index = value.Length * 8 - 1; index >= 0; index--)
				if (GetBit(value, index))
					return index + 1;
			return 0;
		}

		/// <summary>
		///     0 rounds to 0, 1 to 8 rounds to 8, 9 to 16 rounds to 16, etc.
		/// </summary>
		public static int RoundUpToMultipleOfEight(int value) {
			return value / 8 * 8 + (value % 8 == 0 ? 0 : 8);
		}

		/// <summary>
		///     Set a single bit to 1 or 0 without affecting the rest of the source byte array.
		/// </summary>
		/// <param name="source"> </param>
		/// <param name="offsetInBits"> </param>
		/// <param name="value"> True means 1. False means 0. </param>
		public static void SetBit(byte[] source, int offsetInBits, bool value) {
			// First clear the bit.
			var mask = (byte)(0xFF ^ (1 << offsetInBits % 8)); // Looks something like 11110111 with one cleared bit and the rest set.
			source[GetByteIndexForBitIndex(offsetInBits)] &= mask;

			// Now set the bit if needed.
			if (value)
				source[GetByteIndexForBitIndex(offsetInBits)] |= (byte)(1 << offsetInBits % 8);
		}

		public static byte[] SetBits(byte[] sourceBytes, byte[] replacementBits, int numberOfBitsToReplace, int offsetIntoTheOriginal) {
			if (NumberOfSignificantBits(replacementBits) > numberOfBitsToReplace)
				throw new ArgumentException("The number of bits to replace is less than the number of significant bits in the replacement bits.");

			// We need to size the results large enough to contain the combined byte arrays, but no smaller than the original (could be larger than the original).
			var maximumBytes = Math.Max(sourceBytes.Length, RoundUpToMultipleOfEight(offsetIntoTheOriginal + numberOfBitsToReplace) / 8);
			var paddedSourceBytes = GetPaddedByteArray(sourceBytes, maximumBytes);

			for (var index = 0; index < numberOfBitsToReplace; index++)
				SetBit(paddedSourceBytes, index + offsetIntoTheOriginal, GetBit(replacementBits, index));

			return paddedSourceBytes;
		}

		public static byte[] StringToByteArray(string inStr) {
			if (inStr == null)
				throw new ArgumentNullException("String is null.");

			var outBytes = new byte[inStr.Length];
			for (var i = 0; i < inStr.Length; i++)
				outBytes[i] = Convert.ToByte(inStr[i]);
			return outBytes;
		}

		// This comes from Prabhu at this link: http://bytes.com/topic/c-sharp/answers/236808-how-convert-structure-byte-array
		public static byte[] StructureToByteArray(object obj) {
			var len = Marshal.SizeOf(obj);

			var arr = new byte[len];

			var ptr = Marshal.AllocHGlobal(len);

			Marshal.StructureToPtr(obj, ptr, true);

			Marshal.Copy(ptr, arr, 0, len);

			Marshal.FreeHGlobal(ptr);

			return arr;
		}

		// This comes from Prabhu at this link: http://bytes.com/topic/c-sharp/answers/236808-how-convert-structure-byte-array
		public static void ByteArrayToStructure(byte[] bytearray, ref object obj) {
			var len = Marshal.SizeOf(obj);

			var i = Marshal.AllocHGlobal(len);

			Marshal.Copy(bytearray, 0, i, len);

			obj = Marshal.PtrToStructure(i, obj.GetType());

			Marshal.FreeHGlobal(i);
		}
	}
}
